namespace OFX.Aggregator.Implementation
{
	#region #using Directives

	using System;
	using System.Diagnostics;
	using System.IO;

	using Infrastructure;

	#endregion

	public sealed class StatementProcessingWorkflow : IStatementProcessingWorkflow
	{
		private readonly IStatementAction action;
		private readonly IIocContainerAbstraction container;
		private readonly StatementSource statementSource;

		public StatementProcessingWorkflow(StatementSource statementSource, IStatementAction action, IIocContainerAbstraction container)
		{
			this.container = container;
			this.action = action;
			this.statementSource = statementSource;
		}

		#region Implementation of IStatementProcessingWorkflow

		public void PerformWorkflow()
		{
			if (WorkflowStarted != null)
			{
				WorkflowStarted(this, EventArgs.Empty);
			}

			retrieveStatement();
		}

		public StatementSource StatementSource
		{
			[DebuggerStepThrough]
			get { return this.statementSource; }
		}

		public IStatementAction Action
		{
			[DebuggerStepThrough]
			get { return this.action; }
		}

		public DateTime? DateStart
		{
			[DebuggerStepThrough]
			get { return this.statementSource.LowerCap; }
		}

		public DateTime? DateEnd
		{
			[DebuggerStepThrough]
			get { return null; }
		}

		public event EventHandler WorkflowStarted;
		public event EventHandler WorkflowFinished;

		private void retrieveStatement()
		{
			var dateStart = this.statementSource.LowerCap;
			var asyncStatementRetrieval = this.statementSource.Retrieval as IAsyncStatementRetrieval;
			if (asyncStatementRetrieval != null)
			{
				// Asynchronous retrieval
				asyncStatementRetrieval.BeginGetStatement(this.container, this, onEndRetrieveStatement);
			}
			else
			{
				// Synchronous retrieval
				actOnStatement(delegate { return this.statementSource.Retrieval.GetStatement(dateStart, null); });

				var progressReport = this.container.Resolve<IProgressReport>();
				progressReport.OnFinish();
			}
		}

		private void onEndRetrieveStatement(IAsyncResult asyncResult)
		{
			actOnStatement(delegate { return ((IAsyncStatementRetrieval) this.statementSource.Retrieval).EndGetStatement(asyncResult); });
		}

		private void actOnStatement(Func<Statement> retrieveStatementFunc)
		{
			try
			{
				var statement = retrieveStatementFunc();

				interpretStatement(statement);
			}
			catch (FileNotFoundException fnfex)
			{
				var progressReport = this.container.Resolve<IProgressReport>();
				progressReport.ReportError(fnfex);
			}
			catch (InterpretationFailedException ifex)
			{
				var progressReport = this.container.Resolve<IProgressReport>();
				progressReport.ReportError(ifex);
			}
			finally
			{
				if (WorkflowFinished != null)
				{
					WorkflowFinished(this, EventArgs.Empty);
				}
			}
		}

		private void interpretStatement(Statement statement)
		{
			if (statement != null)
			{
				if (this.statementSource.Postprocessor != null)
				{
					this.statementSource.Postprocessor.Process(statement);
				}

				using (var statementStream = this.statementSource.Serializer.Serialize(statement))
				{
					using (var ofxStream = this.statementSource.OfxConverter.ConvertToOfx(statementStream))
					{
						this.action.DoAction(ofxStream);

						this.statementSource.LowerCap = statement.DateEnd;
						if ((DateTime.Now - statement.DateEnd) < this.statementSource.TransactionListingLag)
						{
							// Allow extra time for statement providers that may take that long to make transactions available
							this.statementSource.LowerCap -= this.statementSource.TransactionListingLag;
						}
						this.statementSource.MarkUpdated();
					}
				}
			}
		}

		#endregion
	}
}